home *** CD-ROM | disk | FTP | other *** search
- package HTML::Parser;
-
-
- =head1 NAME
-
- HTML::Parser - SGML parser class
-
- =head1 SYNOPSIS
-
- require HTML::Parser;
- $p = HTML::Parser->new; # should really a be subclass
- $p->parse($chunk1);
- $p->parse($chunk2);
- $p->eof; # signal end of document
-
- $p->parse_file("foo.html");
- open(F, "foo.html") || die;
- $p->parse_file(\*F);
-
- =head1 DESCRIPTION
-
- The C<HTML::Parser> will tokenize a HTML document when the $p->parse()
- method is called. The document to parse can be supplied in arbitrary
- chunks. Call $p->eof() the end of the document to flush any remaining
- text. The return value from parse() is a reference to the parser
- object.
-
- The $p->parse_file() method can be called to parse text from a file.
- The argument can be a filename or an already opened file handle. The
- return value from parse_file() is a reference to the parser object.
-
- In order to make the parser do anything interesting, you must make a
- subclass where you override one or more of the following methods as
- appropriate:
-
- =over 4
-
- =item $self->declaration($decl)
-
- This method is called when a I<markup declaration> has been
- recognized. For typical HTML documents, the only declaration you are
- likely to find is <!DOCTYPE ...>. The initial "<!" and ending ">" is
- not part of the string passed as argument. Comments are removed and
- entities have B<not> been expanded yet.
-
- =item $self->start($tag, $attr, $attrseq, $origtext)
-
- This method is called when a complete start tag has been recognized.
- The first argument is the tag name (in lower case) and the second
- argument is a reference to a hash that contain all attributes found
- within the start tag. The attribute keys are converted to lower case.
- Entities found in the attribute values are already expanded. The
- third argument is a reference to an array with the lower case
- attribute keys in the original order. The fourth argument is the
- original HTML text.
-
-
- =item $self->end($tag)
-
- This method is called when an end tag has been recognized. The
- argument is the lower case tag name.
-
- =item $self->text($text)
-
- This method is called when plain text in the document is recognized.
- The text is passed on unmodified and might contain multiple lines.
- Note that for efficiency reasons entities in the text are B<not>
- expanded. You should call HTML::Entities::decode($text) before you
- process the text any further.
-
- =item $self->comment($comment)
-
- This method is called as comments are recognized. The leading and
- trailing "--" sequences have been stripped off the comment text.
-
- =back
-
- The default implementation of these methods does nothing, I<i.e.,> the
- tokens are just ignored.
-
- There is really nothing in the basic parser that is HTML specific, so
- it is likely that the parser can parse many kinds of SGML documents,
- but SGML has many obscure features (not implemented by this module)
- that prevent us from renaming this module as C<SGML::Parse>.
-
- =head1 BUGS
-
- You can instruct the parser to parse comments the way Netscape does it
- by calling the netscape_buggy_comment() method with a TRUE argument.
- This means that comments will always be terminated by the first
- occurence of "-->".
-
- =head1 SEE ALSO
-
- L<HTML::TreeBuilder>, L<HTML::HeadParser>, L<HTML::Entities>
-
- =head1 COPYRIGHT
-
- Copyright 1996 Gisle Aas. All rights reserved.
-
- This library is free software; you can redistribute it and/or
- modify it under the same terms as Perl itself.
-
- =head1 AUTHOR
-
- Gisle Aas <aas@sn.no>
-
- =cut
-
-
- use strict;
-
- use HTML::Entities ();
- use vars qw($VERSION);
- $VERSION = sprintf("%d.%02d", q$Revision: 2.6 $ =~ /(\d+)\.(\d+)/);
-
-
- sub new
- {
- my $class = shift;
- my $self = bless { '_buf' => '',
- '_netscape_comment' => 0,
- }, $class;
- $self;
- }
-
-
-
- sub eof
- {
- shift->parse(undef);
- }
-
-
- sub parse
- {
- my $self = shift;
- my $buf = \ $self->{'_buf'};
- unless (defined $_[0]) {
- $self->text($$buf) if length $$buf;
- $$buf = '';
- return $self;
- }
- $$buf .= $_[0];
-
- while (1) { # the loop will end by returning when text is parsed
- if ($$buf =~ s|^([^<]+)||) {
- unless (length $$buf) {
- my $text = $1;
- if ($text =~ s|(\s+)$||) {
- $$buf = $1;
- } elsif ($text =~ s/(&(?:(?:\#\d*)?|\w*))$//) {
- $$buf = $1;
- };
- $self->text($text);
- return $self;
- } else {
- $self->text($1);
- }
- } elsif ($self->{'_netscape_comment'} && $$buf =~ m|^(<!--)|) {
- if ($$buf =~ s|^<!--(.*?)-->||s) {
- $self->comment($1);
- } else {
- return $self; # must wait until we see the end of it
- }
- } elsif ($$buf =~ s|^(<!)||) {
- my $eaten = $1;
- my $text = '';
- my @com = (); # keeps comments until we have seen the end
- while ($$buf =~ s|^(([^>]*?)--)||) {
- $eaten .= $1;
- $text .= $2;
- if ($$buf =~ s|^((.*?)--)||s) {
- $eaten .= $1;
- push(@com, $2);
- } else {
- $$buf = $eaten . $$buf;
- return $self;
- }
- }
- if ($$buf =~ s|^([^>]*)>||) {
- $text .= $1;
- $self->declaration($text) if $text =~ /\S/;
- for (@com) { $self->comment($_); }
- } else {
- $$buf = $eaten . $$buf; # must start with it all next time
- return $self;
- }
- } elsif ($$buf =~ s|^</||) {
- if ($$buf =~ s|^([a-zA-Z][a-zA-Z0-9\.\-]*)\s*>||) {
- $self->end(lc($1));
- } elsif ($$buf =~ m|^[a-zA-Z]*[a-zA-Z0-9\.\-]*\s*$|) {
- $$buf = "</" . $$buf; # need more data to be sure
- return $self;
- } else {
- $self->text("</");
- }
- } elsif ($$buf =~ s|^<||) {
- my $eaten = '<';
-
- if ($$buf =~ s|^(([a-zA-Z][a-zA-Z0-9\.\-]*)\s*)||) {
- $eaten .= $1;
- my $tag = lc $2;
- my %attr;
- my @attrseq;
-
- while ($$buf =~ s|^(([a-zA-Z][a-zA-Z0-9\.\-_]*)\s*)||) {
- $eaten .= $1;
- my $attr = lc $2;
- my $val;
- if ($$buf =~ s|(^=\s*([^\"\'>\s][^>\s]*)\s*)||) {
- $eaten .= $1;
- $val = $2;
- HTML::Entities::decode($val);
- } elsif ($$buf =~ s|(^=\s*([\"\'])(.*?)\2\s*)||s) {
- $eaten .= $1;
- $val = $3;
- HTML::Entities::decode($val);
- } elsif ($$buf =~ m|^(=\s*)$| or
- $$buf =~ m|^(=\s*[\"\'].*)|s) {
- $$buf = "$eaten$1";
- return $self;
- } else {
- $val = $attr;
- }
- $attr{$attr} = $val;
- push(@attrseq, $attr);
- }
-
- if ($$buf =~ s|^>||) {
- $self->start($tag, \%attr, \@attrseq, "$eaten>");
- } elsif (length $$buf) {
- $self->text($eaten);
- } else {
- $$buf = $eaten; # need more data to know
- return $self;
- }
-
- } elsif (length $$buf) {
- $self->text($eaten);
- } else {
- $$buf = $eaten . $$buf; # need more data to parse
- return $self;
- }
-
- } elsif (length $$buf) {
- die; # This should never happen
- } else {
- return $self;
- }
- }
- $self;
- }
-
- sub netscape_buggy_comment
- {
- my $self = shift;
- my $old = $self->{'_netscape_comment'};
- $self->{'_netscape_comment'} = shift if @_;
- return $old;
- }
-
- sub parse_file
- {
- my($self, $file) = @_;
- no strict 'refs'; # so that a symbol ref as $file works
- local(*F);
- unless (ref($file) || $file =~ /^\*[\w:]+$/) {
- open(F, $file) || die "Can't open $file: $!";
- $file = \*F;
- }
- my $chunk = '';
- while(read($file, $chunk, 2048)) {
- $self->parse($chunk);
- }
- close($file);
- $self->eof;
- }
-
- sub text
- {
- }
-
- sub declaration
- {
- }
-
- sub comment
- {
- }
-
- sub start
- {
- my($self, $tag, $attr, $attrseq, $origtext) = @_;
- }
-
- sub end
- {
- my($self, $tag) = @_;
- }
-
- 1;
-